/*
 * $Id: inet.c,v 1.20 2005-12-14 04:46:55 bacon Exp $
 */

#include <xp/net/inet.h>
#include <xp/bas/endian.h>
#include <xp/bas/ctype.h>
#include <xp/bas/string.h>
#include <xp/bas/assert.h>

xp_byte_t xp_inet4_any[4] = { 0, 0, 0, 0 };
xp_byte_t xp_inet4_loopback[4] = { 127, 0, 0, 1 };

xp_byte_t xp_inet6_any[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
xp_byte_t xp_inet6_loopback[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 };

int xp_inet4_strtoip (const xp_char_t* str, xp_uint32_t* ip)
{
	xp_char_t c;
	int dots = 0, digits = 0;
	xp_uint32_t acc = 0, addr = 0;	

	while (1) {
		c = *str;	

		if (c == XP_CHAR('\0')) {
			if (dots < 3 || digits == 0) return -1;
			addr = (addr << 8) | acc;
			break;
		}
		else if (c >= XP_CHAR('0') && c <= XP_CHAR('9')) {
			if (digits > 0 && acc == 0) return -1;
			acc = acc * 10 + (c - XP_CHAR('0'));
			if (acc > 255) return -1;
			digits++;
		}
		else if (c == XP_CHAR('.')) {
			if (dots >= 3 || digits == 0) return -1;
			addr = (addr << 8) | acc;
			dots++; acc = 0; digits = 0;
		}
		else return -1;

		str++;
	}

	if (ip != XP_NULL) *ip = xp_hton32(addr);
	return 0;
}

int xp_inet4_strxtoip (const xp_char_t* str, xp_size_t len, xp_uint32_t* ip)
{
	xp_char_t c;
	int dots = 0, digits = 0;
	xp_uint32_t acc = 0, addr = 0;	
	const xp_char_t* end = str + len;

	while (1) {
		if (str >= end) {
			if (dots < 3 || digits == 0) return -1;
			addr = (addr << 8) | acc;
			break;
		}

		c = *str;	

		if (c >= XP_CHAR('0') && c <= XP_CHAR('9'))  {
			if (digits > 0 && acc == 0) return -1;
			acc = acc * 10 + (c - XP_CHAR('0'));
			if (acc > 255) return -1;
			digits++;
		}
		else if (c == XP_CHAR('.')) {
			if (dots >= 3 || digits == 0) return -1;
			addr = (addr << 8) | acc;
			dots++; acc = 0; digits = 0;
		}
		else return -1;

		str++;
	} 

	if (ip != XP_NULL) *ip = xp_hton32(addr);
	return 0;
}

#define __BTOA(b,p,end) \
	do { \
		xp_char_t* sp = p; \
		do {  \
			if (p >= end) { \
				if (p == sp) break; \
				if (p - sp > 1) p[-2] = p[-1]; \
				p[-1] = (b % 10) + XP_CHAR('0'); \
			} \
			else *p++ = (b % 10) + XP_CHAR('0'); \
			b /= 10; \
		} while (b > 0); \
		if (p - sp > 1) { \
			xp_char_t t = sp[0]; \
			sp[0] = p[-1]; \
			p[-1] = t; \
		} \
	} while (0);

#define __ADDDOT(p, end) \
	do { \
		if (p >= end) break; \
		*p++ = XP_CHAR('.'); \
	} while (0)


xp_size_t xp_inet4_iptostrx (xp_uint32_t ip, xp_char_t* buf, xp_size_t size)
{
	xp_byte_t b;
	xp_char_t* p = buf, * end = buf + size - 1;

	if (size == 0) return 0;
	xp_assert (xp_sizeof(ip) == 4);

#if defined(XP_ENDIAN_BIG)
	b = (ip >> 24) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >> 16) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >>  8) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >>  0) & 0xFF; __BTOA (b, p, end);
#elif defined(XP_ENDIAN_LITTLE)
	b = (ip >>  0) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >>  8) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >> 16) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
	b = (ip >> 24) & 0xFF; __BTOA (b, p, end);
#else
	if (xp_is_endian_big()) {
		b = (ip >> 24) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >> 16) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >>  8) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >>  0) & 0xFF; __BTOA (b, p, end);
	}
	else {
		b = (ip >>  0) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >>  8) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >> 16) & 0xFF; __BTOA (b, p, end); __ADDDOT (p, end);
		b = (ip >> 24) & 0xFF; __BTOA (b, p, end);
	}
#endif

	*p = XP_CHAR('\0');
	return p - buf;
}

int xp_inet4_pxtonm (int prefix, xp_uint32_t* netmask)
{
	static xp_uint32_t __nmtab[] =
	{
		0x00000000,
		0x80000000, 0xC0000000, 0xE0000000, 0xF0000000,
		0xF8000000, 0xFC000000, 0xFE000000, 0xFF000000,
		0xFF800000, 0xFFC00000, 0xFFE00000, 0xFFF00000,
		0xFFF80000, 0xFFFC0000, 0xFFFE0000, 0xFFFF0000,
		0xFFFF8000, 0xFFFFC000, 0xFFFFE000, 0xFFFFF000,
		0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, 0xFFFFFF00,
		0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0, 0xFFFFFFF0,
		0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF
	};

	if (prefix < 0 || prefix > 32) return -1;	
	if (netmask != XP_NULL) *netmask = xp_hton32(__nmtab[prefix]);

	return 0;

	/*
	int p, k;
	xp_uint32_t mask = 0;

	for (k = 24; prefix > 0; k -= 8) {
		p = (prefix >= 8)? 0: (8 - prefix);
		mask |= ((0xFF << p) & 0xFF) << k;
		prefix -= 8;
	}

	if (netmask != XP_NULL) *netmask = xp_hton32(mask);
	return 0;
	*/
}

/*
#define __COUNTUP(x,cnt,rest) \
	switch (x) { \
	case 0xFF000000: cnt += 8; break; \
	case 0x00000000: if (rest) return -1; break; \
	case 0xFE000000: cnt += 7; if (rest) return -1; break; \
	case 0xFC000000: cnt += 6; if (rest) return -1; break; \
	case 0xF8000000: cnt += 5; if (rest) return -1; break; \
	case 0XF0000000: cnt += 4; if (rest) return -1; break; \
	case 0xE0000000: cnt += 3; if (rest) return -1; break; \
	case 0xC0000000: cnt += 2; if (rest) return -1; break; \
	case 0x80000000: cnt += 1; if (rest) return -1; break; \
	default: return -1; \
	}
*/

#define __COUNTUP(x,cnt,rest) \
	switch (x) { \
	case 0xFF000000: \
		cnt += 8; break; \
	case 0x00000000: \
		if (rest) return -1; break; \
	case 0xFE000000: cnt++; \
	case 0xFC000000: cnt++; \
	case 0xF8000000: cnt++; \
	case 0XF0000000: cnt++; \
	case 0xE0000000: cnt++; \
	case 0xC0000000: cnt++; \
	case 0x80000000: cnt++; \
		if (rest) return -1; break; \
	default: return -1; \
	}

int xp_inet4_nmtopx (xp_uint32_t netmask, int* prefix)
{
	int pfx = 0;
	xp_uint32_t x;

	netmask = xp_ntoh32(netmask);

	xp_assert (xp_sizeof(netmask) == 4);

	if (netmask) {
		x = netmask & 0xFF000000; netmask <<= 8;
		__COUNTUP (x, pfx, netmask);
	}
	if (netmask) {
		x = netmask & 0xFF000000; netmask <<= 8;
		__COUNTUP (x, pfx, netmask);
	}
	if (netmask) {
		x = netmask & 0xFF000000; netmask <<= 8;
		__COUNTUP (x, pfx, netmask);
	}
	if (netmask) {
		x = netmask & 0xFF000000; 
		__COUNTUP (x, pfx, 0);
	}

	if (prefix != XP_NULL) *prefix = pfx;
	return 0;
}

int xp_inet4_strtoipnm (
	const xp_char_t* str, xp_uint32_t* ip, xp_uint32_t* mask)
{
	const xp_char_t* slash, * p;
	xp_uint32_t tip, tmask;

	slash = xp_strchr (str, XP_CHAR('/'));
	if (slash == XP_NULL) {
		if (xp_inet4_strtoip(str, &tip) == -1) return -1;
		tmask = XP_TYPE_MAX(xp_uint32_t);
	}
	else {
		if (xp_inet4_strxtoip(str, slash - str, &tip) == -1) return -1;

		if (xp_strchr(slash + 1, XP_CHAR('.')) == XP_NULL) {
			int prefix = 0;

			p = slash + 1;
			if (*p == XP_CHAR('\0')) return -1;
			while (xp_isdigit(*p)) {
				prefix = prefix * 10 + (*p - XP_CHAR('0'));
				if (prefix > 32) return -1;
				p++;
			}
			if (*p != XP_CHAR('\0')) return -1;
			xp_inet4_pxtonm (prefix, &tmask);
		}
		else {
			if (xp_inet4_strtoip(slash + 1, &tmask) == -1) return -1;
		}
	}

	if (ip != XP_NULL) *ip = tip;
	if (mask != XP_NULL) *mask = tmask;
	return 0;
}

int xp_inet4_strxtoipnm (
	const xp_char_t* str, xp_size_t len, xp_uint32_t* ip, xp_uint32_t* mask)
{
	const xp_char_t* slash, * p;
	const xp_char_t* end = str + len;
	xp_uint32_t tip, tmask;

	slash = xp_strxchr (str, len, XP_CHAR('/'));
	if (slash == XP_NULL) {
		if (xp_inet4_strxtoip(str, len, &tip) == -1) return -1;
		tmask = XP_TYPE_MAX(xp_uint32_t);
	}
	else {
		if (xp_inet4_strxtoip(str, slash - str, &tip) == -1) return -1;

		if (xp_strxchr(slash + 1, end - slash - 1, XP_CHAR('.')) == XP_NULL) {
			int prefix = 0;

			p = slash + 1;
			if (p >= end) return -1;
			while (p < end && xp_isdigit(*p)) {
				prefix = prefix * 10 + (*p - XP_CHAR('0'));
				if (prefix > 32) return -1;
				p++;
			}
			if (p < end) return -1;

			xp_inet4_pxtonm (prefix, &tmask);
		}
		else {
			if (xp_inet4_strxtoip(slash + 1, end - slash - 1, &tmask) == -1) return -1;
		}
	}

	if (ip != XP_NULL) *ip = tip;
	if (mask != XP_NULL) *mask = tmask;
	return 0;
}

xp_size_t xp_inet4_ipnmtostrx (
	xp_uint32_t ip, xp_uint32_t mask, xp_char_t* buf, xp_size_t size)
{
	xp_size_t len;
	
	len = xp_inet4_iptostrx(ip, buf, size);
	buf += len; size -= len;
	if (size > 1) {
		*buf++ = XP_CHAR('/'); size--; len++;
		*buf = XP_CHAR('\0');

		if (size > 1) {
			len += xp_inet4_iptostrx (mask, buf, size);
		}
	}

	return len;
}

xp_size_t xp_inet4_ipnmtostrpx (
	xp_uint32_t ip, xp_uint32_t mask, xp_char_t* buf, xp_size_t size)
{
	xp_char_t* p = buf;
	xp_char_t* end = p + size - 1;
	
	p += xp_inet4_iptostrx(ip, p, size);
	if (p < end) {
		*p++ = XP_CHAR('/'); 
		*p = XP_CHAR('\0');

		if (p < end) {
			int prefix;

			if (xp_inet4_nmtopx(mask, &prefix) == -1) {
				return (xp_size_t)-1;
			}

			xp_assert (prefix <= XP_TYPE_MAX(xp_byte_t));
			__BTOA (prefix, p, end - 1); *p = XP_CHAR('\0');
		}
	}

	return p - buf;
}

#if 0

#define XP_INET_HOST_MAX NI_MAXHOST
#define XP_INET_SERVICE_MAX NI_MAXSERV

/*
 * If host is "", it is resolved to localhost.
 * If host is XP_NULL, it is resolved to INADDR_ANY or IN6ADDR_ANY_INIT.
 */
int xp_inet_resolve (
	const xp_char_t* host, const xp_char_t* service,
	xp_port_t* port, xp_ip_t* ip)
{
	struct addrinfo hints;
	struct addrinfo* info, * p;
#ifndef XP_CHAR_IS_MCHAR
	xp_mchar_t mb_host[XP_INET_HOST_MAX + 1];
	xp_mchar_t mb_service[XP_INET_SERVICE_MAX + 1];
	xp_mchar_t* p_host = XP_NULL;
	xp_mchar_t* p_service = XP_NULL;
#endif

	xp_memset (&hints, 0, xp_sizeof(hints));

	hints.ai_family = family;
	hints.ai_socktype = type;

	if (host == XP_NULL) hints.ai_flags = AI_PASSIVE;
	else if (hosts[0] == XP_CHAR('\0')) host = XP_NULL;

#ifdef XP_CHAR_IS_MCHAR
	n = getaddrinfo (host, service, &hints, &info);
#else
	if (host != XP_NULL) {
		if (xp_wcstomcs (host, 
			mb_host, xp_countof(mb_host)) == 0) {
			*errnum = EAI_NONAME;
			return -1;
		}
		p_host = mb_host;
	}
	if (service != XP_NULL) {
		if (xp_wcstomcs (service, 
			mb_service, xp_countof(mb_service)) == 0) {
			*errnum = EAI_NONAME;
			return -1;
		}
		p_service = mb_service;
	}
	n = getaddrinfo (p_host, p_service, &hints, &info);	
#endif
	if (n != 0) return -1;

	for (p = info; p != XP_NULL; p = p->ai_next) {
		if (family != V0 && p->ai_family != family) continue;
		if (type != 0 && p->ai_socktype != type) continue;
		if (xp_sizeof(address_storage) < p->ai_addrlen) continue;

		xp_memcpy (&address_storage, p->ai_addr, p->ai_addrlen);
		break;
	}

	freeaddrinfo (info);
	return 0;
}

#endif
